/*
 * Copyright (C) 2012 Rothens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package rpg.gfx;

import java.awt.Color;

/**
 * rothens.tarhely.biz
 *
 * @author Rothens
 */
public class Bitmap {

    public int width;
    public int height;
    public int[] pixels;

    public Bitmap(int width, int height) {
        this.width = width;
        this.height = height;
        pixels = new int[width * height];
        for (int i = 0; i < pixels.length; i++) {
            pixels[i] = 0xff00ff;
        }
    }

    public Bitmap(Bitmap b) {
        this(b.width, b.height);
        draw(b, 0, 0);
    }

    public Bitmap(int width, int height, Bitmap fill) {
        this.width = width;
        this.height = height;
        pixels = new int[width * height];
        for (int x = 0; x < width; x += fill.width) {
            for (int y = 0; y < height; y += fill.height) {
                draw(fill, x, y);
            }
        }
    }

    /**
     * Draws a bitmap on the current bitmap. Will replace all pixels in the
     * area.
     *
     * @param bitmap the bitmap to draw
     * @param xOffs
     * @param yOffs
     * @return the new bitmap
     */
    public Bitmap draw(Bitmap bitmap, int xOffs, int yOffs) {
        if (bitmap == null) {
            return this;
        }
        final int minY = Math.max(0, yOffs);
        final int maxY = Math.min(yOffs + bitmap.height, height);
        final int minX = Math.max(0, xOffs);
        final int maxX = Math.min(xOffs + bitmap.width, width);
        for (int y = minY; y < maxY; y++) {
            int offset = y * width + minX;
            int offsetbm = (minX - xOffs) + (y - yOffs) * bitmap.width;

            for (int x = minX; x < maxX; x++) {
                int src = bitmap.pixels[offsetbm];
                if (src != 0xff00ff && src != -65281) {
                    pixels[offset] = src;
                }
                offset++;
                offsetbm++;
            }
        }
        return this;
    }

    public void fill(int color, int fromX, int fromY, int toX, int toY) {
        final int minY = Math.max(0, fromY);
        final int maxY = Math.min(toY + 1, height);
        final int minX = Math.max(0, fromX);
        final int maxX = Math.min(toX + 1, width);
        for (int y = minY; y < maxY; y++) {
            int offset = y * width + minX;
            for (int x = minX; x < maxX; x++) {
                pixels[offset] = color;
                offset++;
            }
        }
    }

    /**
     * Draws a bitmap over the current. No alpha blending. Will replace only the
     * non-transparent pixels.
     *
     * @param bitmap
     * @param xOffs
     * @param yOffs
     */
    public Bitmap drawOn(Bitmap bitmap, int xOffs, int yOffs) {
        final int minY = Math.max(0, yOffs);
        final int maxY = Math.min(yOffs + bitmap.height, height);
        final int minX = Math.max(0, xOffs);
        final int maxX = Math.min(xOffs + bitmap.width, width);
        for (int y = minY; y < maxY; y++) {
            int offset = y * width + minX;
            int offsetbm = (minX - xOffs) + (y - yOffs) * bitmap.width;
            for (int x = minX; x < maxX; x++) {
                int src = bitmap.pixels[offsetbm];
                if (src != 0xff00ff && src != -65281 && src != 0) {
                    pixels[offset] = (0xff << 24) | src;
                }
                offset++;
                offsetbm++;
            }
        }

        return this;
    }

    public void drawOnOpaque(Bitmap bitmap, int xOffs, int yOffs) {
        final int minY = Math.max(0, yOffs);
        final int maxY = Math.min(yOffs + bitmap.height, height);
        final int minX = Math.max(0, xOffs);
        final int maxX = Math.min(xOffs + bitmap.width, width);
        for (int y = minY; y < maxY; y++) {
            int offset = y * width + minX;
            int offsetbm = (minX - xOffs) + (y - yOffs) * bitmap.width;
            for (int x = minX; x < maxX; x++) {
                int src = bitmap.pixels[offsetbm];
                if ((src & 0xffffff) != 0xff00ff && src != -65281 && src != 0) {
                    pixels[offset] = (0xff << 24) | src;
                }
                offset++;
                offsetbm++;
            }
        }
    }

    public void drawOnBlend(Bitmap bitmap, int xOffs, int yOffs) {
        final int minY = Math.max(0, yOffs);
        final int maxY = Math.min(yOffs + bitmap.height, height);
        final int minX = Math.max(0, xOffs);
        final int maxX = Math.min(xOffs + bitmap.width, width);
        for (int y = minY; y < maxY; y++) {
            int offset = y * width + minX;
            int offsetbm = (minX - xOffs) + (y - yOffs) * bitmap.width;
            for (int x = minX; x < maxX; x++) {
                int src = bitmap.pixels[offsetbm];
                /*if ((src & 0xff000000) == 0xff000000) {
                    if ((src & 0xffffff) != 0xff00ff && src != -65281 && src != 0) {
                        pixels[offset] = (0xff << 24) | src;
                    }
                } else */if ((src & 0xffffff) != 0xff00ff && src != -65281 && src != 0 && (src&0xff000000) != 0) {
                    pixels[offset] = alphaBlend(pixels[offset], src);
                }
                offset++;
                offsetbm++;
            }
        }
    }

    public void desaturate() {
        for (int i = 0; i < width * height; i++) {
            int src = pixels[i];
            int r = (src & 0xFF0000) >> 16;
            int g = (src & 0xFF00) >> 8;
            int b = (src & 0xFF);
            int avg = ((r + g + b) / 3) & 0xFF;
            pixels[i] = (avg << 16) | (avg << 8) | avg;

        }
    }

    /**
     * Rotates a Bitmap to the left. (Counter-clockwise)
     */
    public Bitmap rotate() {
        Bitmap ret = new Bitmap(height, width);
        for (int i = 0; i < height * width; i++) {
            int col = i % width;
            int row = i / width;
            ret.pixels[((width - col) * height) - ((height - row))] = pixels[i];
        }
        return ret;
    }

    /**
     * Rotates a bitmap in the center by a given angle
     *
     * @param angle angle in degree
     * @return Bitmap
     */
    public Bitmap rotate(double angle) {
        Bitmap ret = new Bitmap(width, height);
        final double radians = Math.toRadians(angle);
        final double cos = Math.cos(radians);
        final double sin = Math.sin(radians);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int cX = width / 2;
                int cY = height / 2;
                int m = x - cX;
                int n = y - cY;
                int j = ((int) (m * cos + n * sin)) + cX;
                int k = ((int) (n * cos - m * sin)) + cY;
                if (j >= 0 && j < width && k >= 0 && k < height) {
                    ret.pixels[(y * width + x)] = pixels[(k * width + j)];
                }
            }
        }
        return ret;
    }

    public void clear() {
        for (int i = 0; i < height * width; i++) {
            pixels[i] = 0xff00ff;
        }
    }

    public void clearWith(int c) {
        for (int i = 0; i < height * width; i++) {
            pixels[i] = c;
        }
    }

    public Bitmap simpleRecolor(int from, int to) {
        Bitmap ret = new Bitmap(width, height);
        for (int i = 0; i < pixels.length; i++) {
            if (pixels[i] == from) {
                ret.pixels[i] = to;
            } else {
                ret.pixels[i] = pixels[i];
            }
        }
        return ret;
    }
    
    public void setAlpha(byte alpha){
        for(int i = 0; i < pixels.length; i++){
            pixels[i] = (pixels[i]&0xffffff) | (alpha << 24);
        }
    }

    public final int alphaBlend(int background, int newpixel) {

        final int alpha_new = (newpixel >> 24) & 0xff;

        final int alpha_bg = 256 - alpha_new;

        final int rr = background & 0xff0000;
        final int gg = background & 0xff00;
        final int bb = background & 0xff;

        int r = (newpixel & 0xff0000);
        int g = (newpixel & 0xff00);
        int b = (newpixel & 0xff);

        r = ((r * alpha_new + rr * alpha_bg) >> 8) & 0xff0000;
        g = ((g * alpha_new + gg * alpha_bg) >> 8) & 0xff00;
        b = ((b * alpha_new + bb * alpha_bg) >> 8) & 0xff;

        return 0xff000000 | r | g | b;
    }

    /**
     * Removes the border of the given Bitmap
     *
     * @param val
     * @return
     */
    public Bitmap inset(int val) {
        fill(0xffff00ff, 0, 0, width, val);
        fill(0xffff00ff, 0, 0, val, height);
        fill(0xffff00ff, 0, height - val - 1, width, height);
        fill(0xffff00ff, width - val - 1, 0, width, height);
        return this;
    }
    
    

    public Bitmap colorizeBitmap(int color) {
        Bitmap b = new Bitmap(width, height);
        float[] colorHSB = new float[3];
        colorHSB = Color.RGBtoHSB((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, colorHSB);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int src = pixels[x + y * width];
                if ((src & 0xffffff) != 0xff00ff && src != -65281 && src != 0) {
                    float[] pixelHSB = new float[3];
                    pixelHSB = Color.RGBtoHSB((src >> 16) & 0xFF, (src >> 8) & 0xFF, src & 0xFF, pixelHSB);
                    b.pixels[x + y * width] = Color.HSBtoRGB(colorHSB[0], pixelHSB[1], pixelHSB[2]);
                }

            }
        }

        return b;
    }

    public int getAverageColor() {
        int r = 0;
        int g = 0;
        int b = 0;
        for (int c : pixels) {
            r += (c >> 16) & 0xff;
            g += (c >> 8) & 0xff;
            b += c & 0xff;
        }
        r /= pixels.length;
        g /= pixels.length;
        b /= pixels.length;
        return r << 16 | g << 8 | b;
    }
}
